/*
 * IdentityComonentDefinition.java
 *
 * Created on September 2, 2006, 4:37 PM
 *
 * (C) 2006 R. Alexander Milowski
 */

package org.infoset.component.library;

import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import org.infoset.component.Component;
import org.infoset.component.ComponentBase;
import org.infoset.component.ComponentDefinitionBase;
import org.infoset.component.library.WrapComponentDefinition.WrapComponent;
import org.infoset.xml.Characters;
import org.infoset.xml.Child;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.filter.RemoveDocumentFilter;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 *
 * @author alex
 */
public class ParseComponentDefinition extends ComponentDefinitionBase {
   
   static Logger LOG = Logger.getLogger(ParseComponentDefinition.class.getName());
   static Name DOCUMENT_INPUT_PORT = Name.create("source");
   static Name RESULT_OUTPUT_PORT = Name.create("result");
   static Name CTYPE_PARAMETER = Name.create("content-type");
   static Name DEFAULT_NAMESPACE_PARAMETER = Name.create("default-namespace");
   static List<Name> inputs = new ArrayList<Name>();
   static List<Name> outputs = new ArrayList<Name>();
   static {
      inputs.add(DOCUMENT_INPUT_PORT);
      outputs.add(RESULT_OUTPUT_PORT);
   }
   
   static class TagSoupXMLReaderInstantiator implements SAXDocumentLoader.XMLReaderInstantiator {
      public XMLReader newInstance()
         throws SAXException
      {
         return XMLReaderFactory.createXMLReader("org.ccil.cowan.tagsoup.Parser");
      }
   }
   
   public class ParseComponent extends ComponentBase {
      
      Document inputDocument;
      ItemDestination output;
      String contentType;
      Name name;
      String defaultNamespace;
      
      public ParseComponent(Name name,String vendor,String version) {
         super(name,vendor,version);
      }
      
      public void init(Component.Context context)
         throws XMLException
      {
         super.init(context);
         Object o = context.getParameter(CTYPE_PARAMETER);
         contentType = o==null ? "text/xml" : o.toString();
         o = context.getParameter(DEFAULT_NAMESPACE_PARAMETER);
         defaultNamespace = o==null ? "" : o.toString();
      }
      
      public void bindResource(Name port,Document document)
         throws XMLException 
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            inputDocument = document;
         } else {
            throw new XMLException("Binding port "+port+" via ItemSource is not supported.");
         }
      }
   
      public void bindResource(Name port,URI location)
         throws XMLException
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            try {
               inputDocument = context.getDocumentLoader().load(location);
            } catch (IOException ex) {
               throw new XMLException("Cannot load input document due to I/O error.",ex);
            }
         } else {
            super.bindResource(port,location);
         }
      }
      public void bindOutputPort(Name port,ItemDestination output)
         throws XMLException
      {
         if (port.equals(RESULT_OUTPUT_PORT)) {
            this.output = output;
         } else {
            throw new XMLException("Unknown output port "+port+" on "+name);
         }
      }

      
      public void onStart() 
         throws XMLException
      {
         if (inputDocument!=null) {
            generateFromDocuments();
         }
      }
      
      public void onEnd() {
         inputDocument = null;
      }
      
      void generateFromString(URI baseURI,String value) 
         throws XMLException
      {
         try {
            value = "<root xmlns='"+defaultNamespace+"'>"+value+"</root>";
            ItemDestination dest = new ItemDestination() {
               int level = -1;
               public void send(Item item)
                  throws XMLException
               {
                  switch (item.getType()) {
                     case DocumentItem:
                     case ElementItem:
                        level++;
                        if (level>1) {
                           output.send(item);
                        }
                        break;
                     case DocumentEndItem:
                     case ElementEndItem:
                        if (level>1) {
                           output.send(item);
                        }
                        level--;
                        break;
                     default:
                        if (level>=1) {
                           output.send(item);
                        }
                  }
               }
            };
            if (contentType.equals("text/xml") ||
                 contentType.equals("application/xml") ||
                 contentType.endsWith("+xml")) {
                DocumentLoader docLoader = new SAXDocumentLoader();
                docLoader.generate(
                   new StringReader(value),
                   baseURI,
                   dest
                );
            } else if (contentType.equals("text/html")) {
                DocumentLoader docLoader = new SAXDocumentLoader(new TagSoupXMLReaderInstantiator());
                docLoader.generate(
                   new StringReader(value),
                   baseURI,
                   dest
                );
            } else {
               throw new XMLException("Unrecognized content type "+contentType);
            }
         } catch (IOException ex) {
            throw new XMLException("I/O error while parsing string?",ex);
         }
         
      }
      
      void generateFromDocuments() 
         throws XMLException
      {
         ItemConstructor constructor = context.getInfoset().createItemConstructor();
         URI baseURI = inputDocument.getBaseURI();
         output.send(inputDocument);
         Name name = inputDocument.getDocumentElement().getName();
         output.send(inputDocument.getDocumentElement());
         StringBuilder builder = new StringBuilder();
         for (Child c : inputDocument.getDocumentElement()) {
            if (c instanceof Characters) {
               builder.append(((Characters)c).getText());
            }
         }
         generateFromString(baseURI,builder.toString());
         output.send(constructor.createElementEnd(name));
         output.send(constructor.createDocumentEnd());
      }
      
      public ItemDestination getInputPort(Name port) 
         throws XMLException
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            return new ItemDestination() {
               int level = -1;
               ItemConstructor constructor;
               StringBuilder builder;
               URI baseURI;
               public void send(Item item)
                  throws XMLException 
               {
                  switch (item.getType()) {
                     case DocumentItem:
                     case DocumentEndItem:
                        output.send(item);
                        break;
                     case ElementItem:
                        level++;
                        if (level==0) {
                           builder = new StringBuilder();
                           baseURI = ((Element)item).getBaseURI();
                           output.send(item);
                        }
                        break;
                     case CharactersItem:
                        if (level>=0) {
                           builder.append(((Characters)item).getText());
                        }
                        break;
                     case ElementEndItem:
                        if (level==0) {
                           generateFromString(baseURI,builder.toString());
                           output.send(item);
                        }
                        level--;
                        break;
                  }
               }
            };
         } else {
            super.getInputPort(port);
         }
         return null;
      }
      
      public void onDocumentInput(Name port,Document document) 
         throws XMLException
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            if (inputDocument!=null) {
               throw new XMLException("The input document has already be receieved.");
            }
            inputDocument = document;
            
            generateFromDocuments();
         } else {
            super.getInputPort(port);
         }
      }
      
   }
   
   
   /** Creates a new instance of IdentityComonentDefinition */
   public ParseComponentDefinition(Name name) {
      super(name,null,null,null);
   }
   
   /** Creates a new instance of IdentityComonentDefinition */
   public ParseComponentDefinition(Name name,String vendor,String version,URI vendorLocation) {
      super(name,vendor,version,vendorLocation);
   }
   
   public Component newInstance() 
      throws XMLException
   {
      return new ParseComponent(getName(),getVendor(),getVersion());
   }
   
   public Collection<Name> getInputs() {
      return inputs;
   }
   
   public Collection<Name> getOutputs() {
      return outputs;
   }

}
